using System;
using System.Text;
using System.IO;
using System.Reflection;
using System.Configuration;
using System.Collections.Generic;
using System.Threading;
using System.Windows.Forms;

namespace BrianLyttle.Samples.WinForms
{
    public class CrashManager
    {
        private static string appTitle;
        /// <summary>
        /// Internal flag indicating whether CrashHandling is enabled.
        /// </summary>
        private static bool isEnabled;
        
        /// <summary>
        /// Internal list of basic information snippets on crash.
        /// </summary>
        private static List<CrashDataItem> basicInfo;

        /// <summary>
        /// Internal representation of the stack trace.
        /// </summary>
        private static string stackTrace;

        /// <summary>
        /// Internal reference to the assembly where the crash occurred.
        /// </summary>
        private static Assembly parentAssembly;

        /// <summary>
        /// Registers the crash handler with default response option. Loads
        /// user settings and opt-out information before initialization.
        /// </summary>
        public static void Register()
        {
            // Load settings
            isEnabled = true;
            // Get application title
            appTitle = AppDomain.CurrentDomain.FriendlyName;
            
            if (isEnabled)
            {
                Initialize();
            }
        }

        /// <summary>
        /// Registers the crash handler with a custom response option. Loads
        /// user settings and opt-out information before initialization.
        /// </summary>
        /// <param name="Response">CrashResponse enumeration value.</param>
        public static void Register(CrashResponse Response)
        {
            throw new NotImplementedException();
            //switch (Response)
            //{
            //    case CrashResponse.Default:
            //        HandleCrash();
            //        break;
            //    case CrashResponse.AutoSubmit:
            //        HandleCrashAutoSubmit();
            //        break;
            //    case CrashResponse.SilentAutoSubmit:
            //        HandleCrashSilentAutoSubmit();
            //        break;
            //}
            //// Load settings
            //Initialize();
        }
 
        /// <summary>
        /// Sets up the handler for various error events, and gets required references.
        /// </summary>
        private static void Initialize()
        {
            // Obtain a reference to the assembly which has registered it's intent to use this service
            CrashManager.GetCallingAssembly();

            // Replace existing exception handles for the main GUI thread and the app domain. In
            // WinForms we have to be careful how this is done since the main app normally throws
            // ThreadExceptions, unlike a console app that just has UnhandledExceptions.
            Application.ThreadException -= 
                                new ThreadExceptionEventHandler(CrashManager.ThreadExceptionHandler);
            Application.ThreadException += 
                                new ThreadExceptionEventHandler(CrashManager.ThreadExceptionHandler);
            AppDomain.CurrentDomain.UnhandledException -= 
                                new UnhandledExceptionEventHandler(CrashManager.UnhandledExceptionHandler);
            AppDomain.CurrentDomain.UnhandledException += 
                                new UnhandledExceptionEventHandler(CrashManager.UnhandledExceptionHandler);
        }

        /// <summary>
        /// ThreadExceptionHandler used for WinForms GUI thread.
        /// </summary>
        /// <param name="e"></param>
        /// <param name="Args"></param>
        private static void ThreadExceptionHandler(object sender, ThreadExceptionEventArgs e)
        {
            HandleCrash(e.Exception);
        }

        /// <summary>
        /// General purpose error handler for threads spun off of the GUI thread.
        /// </summary>
        /// <param name="e"></param>
        /// <param name="Args"></param>
        private static void UnhandledExceptionHandler(object sender, UnhandledExceptionEventArgs e)
        {
            HandleCrash(e.ExceptionObject as Exception);
        }

        /// <summary>
        /// Prepares crash data for display to user or dispatch to collection tool. Do this
        /// before going to a dialog in case we want to auto-submit the data.
        /// </summary>
        private static void PrepareExceptionData(Exception Exception)
        {
            AddInfo("Date/Time", DateTime.Now.ToString());
            AddInfo("Application Domain", AppDomain.CurrentDomain.FriendlyName);
            AddInfo("Assembly Name", parentAssembly.FullName);
            AddInfo("Assembly Version", parentAssembly.GetName().Version.ToString());
            AddInfo("Assembly Code Base", Diagnostics.GetAssemblyCodeBase());
            AddInfo("Assembly File Time", Diagnostics.GetAssemblyFileTime(parentAssembly).ToString());
            AddInfo("OS Version", Diagnostics.GetOSVersion());
            AddInfo("Processor Count", Diagnostics.GetProcessorCount());
            AddInfo("Working Set", Diagnostics.GetWorkingSet());
            AddInfo("Startup Arguments", Diagnostics.GetStartupArguments());
            AddInfo("Exception Source", Exception.Source.ToString());
            AddInfo("Exception Site", Exception.TargetSite.ToString());
            AddInfo("Exception Type", Exception.GetType().Name);
            AddInfo("Exception Message", Exception.Message);

            stackTrace = Diagnostics.GetStackTrace(Exception);
        }

        /// <summary>
        /// Adds a new basic information name/value pair to the list
        /// for display on the more information dialog.
        /// </summary>
        /// <param name="Name">Name of crash info item.</param>
        /// <param name="Value">Value of crash info item.</param>
        private static void AddInfo(string Name, string Value)
        {
            if (basicInfo == null)
            {
                basicInfo = new List<CrashDataItem>();
            }

            CrashDataItem friendlyName = new CrashDataItem(Name, Value);
            basicInfo.Add(friendlyName);
        }

        /// <summary>
        /// Displays a dialog to the user with information on the crash.
        /// </summary>
        private static void DisplayCrashDialog()
        {
            CrashDialog cd = new CrashDialog(appTitle, basicInfo, stackTrace);
            cd.Show();
        }

        /// <summary>
        /// Crash handler for default cases.
        /// </summary>
        private static void HandleCrash(Exception Exception)
        {
            PrepareExceptionData(Exception);
            DisplayCrashDialog();
        }

        /// <summary>
        /// Crash handler for auto-submit.
        /// </summary>
        private static void HandleCrashAutoSubmit(Exception Exception)
        {
            throw new NotImplementedException();
        }

        /// <summary>
        /// Crash handler for silent, auto-submit.
        /// </summary>
        private static void HandleCrashSilentAutoSubmit(Exception Exception)
        {
            throw new NotImplementedException();
        }

        /// <summary>
        /// Obtains a reference to the assembly where crash orginated.
        /// </summary>
        /// <returns>Reference to assembly causing crash.</returns>
        private static Assembly GetCallingAssembly()
        {
            if (CrashManager.parentAssembly == null)
            {
                if (Assembly.GetEntryAssembly() == null)
                {
                    CrashManager.parentAssembly = Assembly.GetCallingAssembly();
                }
                else
                {
                    CrashManager.parentAssembly = Assembly.GetEntryAssembly();
                }
            }
            return CrashManager.parentAssembly;
        }
    }
}
